// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: implementation of CImplSelection.
//   $Id$

#include "stdafx.h"
#include "select.h"

#include "xforms.h"
#include "model.h"
#include "SelectUI.h"
#include "xformsxformselement.h"

#include "boundnode.h"

#include "isEqualNode.h"
#include "commonChild.h"
#include "XFormsElementItem.h"
#include "ImplContextualValue.h"
#include "itemset.h"
#include "item.h"
//
//	HRESULT CImplChoices::MakeOptions(HRESULT (_stdcall*pRenderItem)(BSTR,BSTR,BSTR*), BSTR* psOptions)
//	Removed version 146
//	

CImplSelection::CImplSelection(CXFormsElementSelection* pElement)
	: CImplFormControlInput((CXFormsElementFormControlInput*) pElement)
	,m_bIsDrawn(false)
	,m_lItemCount(0)
	, m_pFragment(NULL)
	,m_bTextFragmentAdded(false)
	, m_bPutValue(false)
	, m_bRefreshing(false)
{
	CFormControl::SetControlType(L"<select>");
	m_pCurrentlySelectedItems = new CMapStringToPtr();
}

const CComBSTR g_sOpen(L"open");
const CComBSTR g_sClosed(L"closed");

bool CImplSelection::IsOpen()
{
	return m_sSelection == g_sOpen;	
}


void CImplSelection::RemoveItem(ISelectionChild * pItem)
{
	//used in the destruction of children
	POSITION pos = m_Items.Find(pItem);
	//It is not an error to be unable to find it,
	//	it may be trying to delete itself from the list
	//	during the destruction of selection
	if(pos)
		m_Items.RemoveAt(pos);

	CImplNodeBinding * pNB = dynamic_cast<CImplNodeBinding*>(pItem);
	if(pNB)
	{
		RemoveControl(pNB);
	}
}

void CImplSelection::AddItem(ISelectionChild* pItem)
{
	m_Items.AddTail(pItem);

	++m_lItemCount;
	m_bIsDrawn = false;
}

CImplSelection::~CImplSelection()
{
	delete m_pCurrentlySelectedItems;
	while(!m_Items.IsEmpty())
	{
		ISelectionChild * pSel = m_Items.RemoveHead();
		CFormControl * pFc = dynamic_cast<CFormControl*>(pSel);
		if(pFc)
		{
//			pFc->m_pParentImpl = 0;
//			pFc->m_pModel = 0;
			pFc->RequestDeleteAtEarliestConvenience();
		}
	}
}

CImplSelectOne::CImplSelectOne(CXFormsElementSelectOne* pElement)
	: CImplSelection((CXFormsElementSelection*) pElement)
{
	CFormControl::SetControlType(L"<select1>");
}

CImplSelectOne::~CImplSelectOne()
{
}





#ifdef _DEBUG
HRESULT CImplSelectOne::AddChild(CImpl *pChild)
{
	HRESULT hr =  __super::AddChild(pChild);
	return hr;
}
#endif

HRESULT CImplSelection::onactivate()
{
	HRESULT hr;
	//first deal with activation
	hr = CImplFormControlInput::onactivate();
	return hr;
}

HRESULT CImplSelectOne::refresh()
{
	HRESULT hr = S_FALSE;
	
	if(!m_bRefreshing)
	{
		m_bRefreshing = true;
		CComPtr<IXMLDOMNode> pNode;
		CComPtr<IXMLDOMNodeList> pNl;
		CComBSTR sText;
		bool bUpdate = false;

		getInstanceNode(&pNode);
		if(pNode)
		{
			DOMNodeType nt;
			pNode->get_nodeType(&nt);
			if(nt == NODE_ATTRIBUTE || nt == NODE_TEXT) {  // Bug 264
				pNode->get_text(&sText);
				bUpdate = true;
				hr = S_OK;
			} else {
				pNode->get_childNodes(&pNl);
				if(pNl) {
					long lNodeCount;
					pNl->get_length(&lNodeCount);
					switch(lNodeCount)
					{
						case 0:
							//do nothing
							onInRange();
							bUpdate = true;
							break;
						case 1:
						{
							hr = S_OK;
							bUpdate = true;
							break;
						}
						default:
							//If there are more than one 
							//	childnodes, then a selectone
							//	cannot disply the value.
							hr = S_OK;
							onOutOfRange();
					}
				}
			}
		}
		FireStateEvents();
		if(!m_bPutValue)
		{
			//update the reliant controls.
			POSITION pos = m_ReliantControls.GetHeadPosition();
			while(pos)
			{
				if(FAILED(m_ReliantControls.GetNext(pos)->refresh()))
					return E_FAIL;
			}
		}

		// Bug 392 - When a xf:select1 appearance="full", refreshes it's children,
		// it overwrite the current radio buttons and results in all the newly
		// added radio buttons to be unselected so not rendered correctly.
		if(bUpdate)
			DoGutsOfRefresh(pNl, sText);
		
		m_bRefreshing = false;
	}
	return hr;
}

void CImplSelectOne::DoGutsOfRefresh(IXMLDOMNodeList *pNL, BSTR sText)
{
	CList<VARIANT, VARIANT> lstValues;
	if(sText) {
		VARIANT v;
		v.vt = VT_BSTR;
		v.bstrVal = ::SysAllocString(sText);
		lstValues.AddTail(v);
	} else if(pNL) {
		CComPtr<IXMLDOMNode> pChild;
		DOMNodeType nt;
		if(pNL->nextNode(&pChild) == S_OK && pChild) {
			pChild->get_nodeType(&nt);
			if(NODE_TEXT == nt) {
				//Get the text and add it to the list.
				BSTR bs;
				pChild->get_text(&bs);					
				VARIANT v;
				v.vt = VT_BSTR;
				v.bstrVal = bs;
				lstValues.AddTail(v);
				pChild.Release();
			} else {
				VARIANT v;
				v.vt = VT_DISPATCH;
				v.pdispVal = pChild.Detach();
				lstValues.AddTail(v);
			}
		}
	}
	FindAndSelect(lstValues);
	if(lstValues.IsEmpty())
		onInRange();
	else {
		onOutOfRange();
		POSITION pos = lstValues.GetHeadPosition();
		while(pos) {
			VARIANT v = lstValues.GetNext(pos);
			switch(v.vt) {
				case VT_DISPATCH:
					v.pdispVal->Release();
					break;
				case VT_BSTR:
					::SysFreeString(v.bstrVal);
			}
		}
		lstValues.RemoveAll();
	}
}


//TODO:
//	These three functions are the same, but because of different
//	paths to CImpl, they are not defined in the base class.
//	when virtual inheritance is fixed, these may no longer be needed.



CImplSelection * CImplItemset::GetOwnerSelection()
{
	ISelectionChild * pParent = dynamic_cast<ISelectionChild*>(m_pParent);
	if(pParent)
	{
		return pParent->GetOwnerSelection();
	}
	else if(m_pParent)
	{
		return dynamic_cast<CImplSelection *>(m_pParent);
	}
	else
	{
		return dynamic_cast<CImplSelection *>(m_pParentImpl);
	}
}



HRESULT CImplSelection::formControlInitialize()
{
	HRESULT hr = CImplFormControlInput::formControlInitialize();
	
	if(SUCCEEDED(hr))
	{
		if(!m_bIsDrawn)
		{
			checkDrawn();
			POSITION pos = m_Items.GetHeadPosition();
			while(pos && SUCCEEDED(hr))
			{
				hr = m_Items.GetNext(pos)->redraw();
			}
		}
	}
	return hr;
}


bool CImplSelection::checkDrawn()
{
	//checks if the control has been drawn, if it hasn't,
	//	it gets drawn by this function.

	//the return value is whether the control had been drawn already(true)
	//	or whether it needed drawing by this fn (false). 
	
	if(m_bIsDrawn)
	{
		return true;
	}
	else
	{
		//prevent infinite recursion 
		m_bIsDrawn = true;
		POSITION pos = m_Items.GetHeadPosition();
		HRESULT hr = S_OK;
		BSTR bs = ::SysAllocString(L"");
		while(pos && SUCCEEDED(hr))
		{
			hr = m_Items.GetNext(pos)->draw(bs);
		}
		::SysFreeString(bs);
		if(FAILED(hr))
			m_bIsDrawn = false;
		return false;
	}


}


CImplCopy::~CImplCopy()
{
	if(m_pParent)
	{
		CImplItemset * pItemset = dynamic_cast<CImplItemset*>(m_pParent);
		if(pItemset)
		{
			pItemset->m_pCopy = 0;
		}
	}
}

CImplValue::~CImplValue()
{
	if(m_pParent)
	{
		//TODO: implement and interface like caption
		CImplItem * pItem = dynamic_cast<CImplItem*>(m_pParent);
		if(pItem)
			pItem->m_pValue = 0;
		else
		{
			CImplItemset * pItemset = dynamic_cast<CImplItemset*>(m_pParent);
			if(pItemset)
			{
				pItemset->m_pValue = 0;
			}
		}
	}
}

CImplValue::CImplValue()
{
}

HRESULT CImplValue::init(CImpl * pParent)
{
	m_bisBound = (dynamic_cast<CImplItemset*>(pParent) == 0);
	return __super::init(pParent);
}

HRESULT CImplValue::refresh()
{
	
	if(m_bShouldRefresh && m_pParent)
	{
		//NOTE:
		//	This could be more generic, with interfaces and stuff,
		//	but since it is only valid for one class at the moment, 
		//	I thought that would be overkill.

		//TODO: copied from CImplCaption - since this is not a cross-cast,
		//	but a direct down cast, I don't know if the dynamic cast is still 
		//	needed.  Probably is, so that NULL is returned from the cast.


		//m_pParent is stored as CImpl, so a dynamic cast is needed.
		//	to reach the CImplItem interface.
		CImplItem * pContainer = dynamic_cast<CImplItem*>(m_pParent);

		if(pContainer)
		{
			//m_pParent is a CImplItem get the
			//	value and tell the parent about it.
			CComBSTR s;
			GetMultipleTextSource(&s);
			HRESULT hr = pContainer->onValueValueChange(s);
			if(hr == S_OK)
			{
				//shouldRefresh is whether to refresh again, if we are from inline or
				//	external data (>xxx< or xlink:href="xxx") don't bother after the first time.
				m_bShouldRefresh = !isEmpty(m_sRef);
			}	
			FireStateEvents();
			return hr;
		}
		else
		{	FireStateEvents();
			return S_FALSE;
		}
	}
	else
	{	
		FireStateEvents();
		return S_OK;
	}
}



HRESULT CImplSelection::refresh()
{
	HRESULT hr = S_OK;

	checkDrawn();

	if(m_pXFormsElement)
	{
		//Fix up the value into ids instead of values.

		CComPtr<IXMLDOMNode> pNode;
		getInstanceNode(&pNode);
		if(pNode)
		{
			CComPtr<IXMLDOMNodeList> pNl;
			pNode->get_childNodes(&pNl);
			if(pNl)
			{
				//Run through the nodelist, selecting
				//	the nodes the items
				//create a list from which we can remove 
				//	nodes when we are finishd.

				// Fix for bug 188 - now using VARIANTs instead of CComVariant*.
				CList<VARIANT, VARIANT> lstValues;
				CComPtr<IXMLDOMNode> pChild;
				DOMNodeType nt;
				while(pNl->nextNode(&pChild) == S_OK && pChild)
				{
					pChild->get_nodeType(&nt);
					
					if(NODE_TEXT == nt)
					{
						//split into substrings.
						BSTR bs;
						pChild->get_text(&bs);					
						//Run through the children selecting them.
						wchar_t * wVal = wcstok(bs, L" ");
						while(wVal)
						{
							VARIANT v;
							v.vt = VT_BSTR;
							v.bstrVal = ::SysAllocString(wVal);
							lstValues.AddTail(v);
							wVal = wcstok(NULL, L" ");
						}
						pChild.Release();
						::SysFreeString(bs);
					}
					else
					{
						VARIANT v;
						v.vt = VT_DISPATCH;
						v.pdispVal = pChild.Detach();
						lstValues.AddTail(v);
					}
				}
				
				FindAndSelect(lstValues);

				if(lstValues.IsEmpty())
				{
					onInRange();
				}
				else
				{
					onOutOfRange();
					POSITION pos = lstValues.GetHeadPosition();
					while(pos)
					{
						VARIANT v = lstValues.GetNext(pos);
						switch(v.vt)
						{
							case VT_DISPATCH:
								v.pdispVal->Release();
								break;
							case VT_BSTR:
								::SysFreeString(v.bstrVal);
						}
					}
					lstValues.RemoveAll();
				}
			}

		}
		//hr = m_pXFormsElement->put_value(sFixedData);	
	}
	FireStateEvents();
	if(!m_bPutValue)
	{

		//update the reliant controls.
		POSITION pos = m_ReliantControls.GetHeadPosition();
		while(pos)
		{
			if(FAILED(m_ReliantControls.GetNext(pos)->refresh()))
				return E_FAIL;
		}
	}
	
	return hr;


#ifdef OLD_SELECT_METHOD
	return CImplSingleNodeBinding::refresh();
#else
	return S_OK;
#endif
}



CResolvedItemList * CImplSelection::GetResolvedItemList()
{
	return &m_ResolvedItems;
}




// hack to implement copy
HRESULT CImplSelection::drawItem(ISelectionChild * pItem , const BSTR sId, const BSTR sCaption, const BSTR sValue, const BSTR sDrawInside, bool bClear)
{
	CString csId(sId);
	m_Items_flat.SetAt(csId,pItem);
	return 	reinterpret_cast<CXFormsElementSelection *>(m_pXFormsElement)
		->AddItem(sId,sCaption, sValue,sDrawInside,true);

}

HRESULT CImplSelection::putValue(const BSTR bsValue)
{
	m_bPutValue = true;

	USES_CONVERSION;
	CBoundNode * pBN = 0;
	if(m_vertices.GetUpperBound() > -1)
		 pBN = m_vertices.GetAt(0)->m_pInstanceNode;
	if(pBN)
	{
		if(!pBN->isReadonly())
		{
			CComPtr<IXMLDOMNode> pNode;
			pBN->getNode(&pNode);
			if(pNode)
			{
				CMapStringToPtr * pPresentlySelectedItems = new CMapStringToPtr();
				CComPtr<IXMLDOMDocument> pDoc;
				pNode->get_ownerDocument(&pDoc);
				if(pDoc)
				{
					pDoc->createDocumentFragment(&m_pFragment);
					m_bTextFragmentAdded = false;
					if(m_pFragment)
					{
						//Run through the children selecting them.
						wchar_t * wId = wcstok(bsValue, L" ");
						while(wId)
						{
							wchar_t * ixCurs = wcschr(wId,L'[');
							long lIx = 0;
							if(ixCurs)
							{
								wchar_t * ixCursEnd =wcsrchr(ixCurs,L']');
								*ixCurs = 0;
								*ixCursEnd = 0;
								//ixCurs++;
								lIx = _wtol(++ixCurs);
								
							}

							CString sId(wId);
							ISelectionChild * pChild = 0;
							if(m_pCurrentlySelectedItems->Lookup(sId,reinterpret_cast<void*&>(pChild)))
							{
								m_pCurrentlySelectedItems->RemoveKey(sId);
							}
							else
							{
								m_Items_flat.Lookup(sId,reinterpret_cast<void*&>(pChild));
							}
							if(pChild)
							{
								pPresentlySelectedItems->SetAt(sId,pChild);
								pChild->select(lIx);
							}
							else if(IsOpen())
							{
								//In the case that no child was found that bears this value, 
								//	first check to see if this is an open selection
								//	then add the string value of this token to the list of values 
								// of this node.
								appendValue(wId);
							}
							wId = wcstok(NULL, L" ");
						}

						pBN->setValue(m_pFragment);
						::safeRelease(m_pFragment);
					}
				}
				
				delete m_pCurrentlySelectedItems;
				m_pCurrentlySelectedItems = pPresentlySelectedItems;
				
				if(m_pModel)
				{
					m_pModel->recalculate(false);
					// Bug 222 - Two refreshes resulting in flickering of the select.
					// m_pModel->refresh(false);
				}
			}
		}
		else
		{
			//the putValue has failed, redisplay the old value.
			refresh();
		}
	}

	m_bPutValue = false;

	return S_OK;
}

void CImplSelection::appendValue(const BSTR sValue)
{
	if(m_pFragment)
	{
		CComPtr<IXMLDOMDocument> pDoc;
		m_pFragment->get_ownerDocument(&pDoc);
		CComPtr<IXMLDOMText> pText;
		CComBSTR bsValue("");
		if(m_bTextFragmentAdded)
			bsValue += L" ";
		bsValue += sValue;
		pDoc->createTextNode(bsValue,&pText);
		CComQIPtr<IXMLDOMNode> pNode = pText;
		m_pFragment->appendChild(pNode,0);
		m_bTextFragmentAdded = true;
	}
}

// <summary>clones the given node and appends it to the instance node.</summary>
void CImplSelection::appendCopy(IXMLDOMNode * pNodeToCopy)
{
	if(m_pFragment && pNodeToCopy)
	{
		CComPtr<IXMLDOMNode> pClone;
		pNodeToCopy->cloneNode(VARIANT_TRUE,&pClone);
		m_pFragment->appendChild(pClone,0);
	}
	else
	{
		ASSERT(0);
	}
}

void CImplSelection::FindAndSelect(CList<VARIANT, VARIANT> &lstValues)
{
	CStringW sFixedValue(L"");
	
	//first iterate through thecurrently selected children.
	POSITION pos = m_pCurrentlySelectedItems->GetStartPosition();
	ISelectionChild *  pChild = 0;
	CString s;
	while(pos)
	{
		m_pCurrentlySelectedItems->GetNextAssoc(pos,s,reinterpret_cast<void*&>(pChild));
		long lPre = lstValues.GetCount();
		if(lPre)
		{
			pChild->selectFrom(lstValues,sFixedValue);
			long lPost = lstValues.GetCount();
			if(lPost == lPre)
				pChild->deselect();
		}
		else
		{
			pChild->deselect();
		}
	}

	pos = m_Items.GetHeadPosition();
	while(pos)
	{
		ISelectionChild * p = m_Items.GetNext(pos);
		
		long lPre = lstValues.GetCount();
		if(lPre)
		{
			p->selectFrom(lstValues,sFixedValue);
			long lPost = lstValues.GetCount();
			if(lPost == lPre)
				p->deselect();
		}
		else
		{
			CImpl * pImpl = dynamic_cast<CImpl*>(p);
			if(pImpl)
			{
				void * pVd = 0;
				m_pCurrentlySelectedItems->Lookup(CString(pImpl->m_sId),pVd);
				if(!pVd)
					p->deselect();
			}
		}
	}
	/*
	disabled, may be causing french crash
	if(!lstValues.IsEmpty())
	{
		VARIANT v = lstValues.GetHead();
		if(v.vt == VT_BSTR)
		{
			sFixedValue.Append(v.bstrVal);
		}
	}
	*/
	if(!sFixedValue.IsEmpty())
		sFixedValue.TrimRight(' ');
	BSTR bs = sFixedValue.AllocSysString();
	m_pXFormsElement->put_value(bs);
	::SysFreeString(bs);
}


